﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace FloatBall
{
    /* 
     * Beskrivning av klassen
     * Denna klass sköter all rendering, kameralogik och kommunikation med grafikkortet.
     * 
     * Vid rendering hämtas positionsdata från de olika klasserna
    */

    static class Render
    {
        //Public constants
        

        //Public variables and objects
        public static Vector3 camPos;
        public static Vector3 camTarget;
        public static Matrix viewMatrix;
        public static Matrix projectionMatrix;
        public static Matrix worldMatrix;

        //Private constants
        private const float MODEL_SCALE = 0.394f;

        
        //Private variables and objects
        private static Vector3 camHeadToPos;
        private static Vector3 camTurnToTarget;
        private static Vector3 camOldPos;
        private static Vector3 camOldTarget;
        private static Vector3 customCamPos;
        private static Vector3 customCamTarget;
        private static float cameraTimer;
        private static long cameraClock = 0;
        private static int cameraMode = 1;

        

        

        private static BasicEffect levelEffect;
        private static BasicEffect boatEffect;
        private static BasicEffect waterEffect; // alternativ att testa: private static EnvironmentMapEffect waterEnvEffect;
        private static BasicEffect[] playerEffect = new BasicEffect[Players.MAX_PLAYERS];
        




        public static void Init()
        {

            camPos = new Vector3(-40f, 200f, 1200f);
            camTarget = new Vector3(Level.LENGTH_X * 0.5f, 0f, Level.WIDTH_Z * 0.5f);

            viewMatrix = Matrix.CreateLookAt(camPos, camTarget, Vector3.Up);
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), Game1.graphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f);
            worldMatrix = Matrix.Identity;


            InitLevelEffect();
            InitBoatEffect();
            InitWaterEffect();

        }




        //Initierar olika basicEffects

        private static void InitLevelEffect()
        {
            //Initialice levelEffect
            levelEffect = new BasicEffect(Game1.graphicsDevice);

            levelEffect.Alpha = 1f;
            levelEffect.DiffuseColor = new Vector3(1f, 1f, 1f);
            levelEffect.SpecularColor = new Vector3(1f, 1f, 1f);
            levelEffect.SpecularPower = 20.0f;
            levelEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);

            levelEffect.DirectionalLight0.Enabled = true;
            levelEffect.DirectionalLight0.DiffuseColor = new Vector3(1.2f, 1.1f, 1f);
            levelEffect.DirectionalLight0.SpecularColor = new Vector3(1f, 1f, 1f);
            levelEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-5f, -2f, 5f));

            levelEffect.DirectionalLight1.Enabled = false;
            levelEffect.DirectionalLight2.Enabled = false;
            
            levelEffect.LightingEnabled = true;
            levelEffect.TextureEnabled = false;

            levelEffect.View = viewMatrix;
            levelEffect.Projection = projectionMatrix;
            levelEffect.World = worldMatrix;


        }

        private static void InitBoatEffect()
        {
            //Initialice levelEffect
            boatEffect = new BasicEffect(Game1.graphicsDevice);

            boatEffect.Alpha = 1f;
            boatEffect.DiffuseColor = new Vector3(1f, 1f, 1f);
            boatEffect.SpecularColor = new Vector3(1f, 1f, 1f);
            boatEffect.SpecularPower = 20.0f;
            boatEffect.AmbientLightColor = new Vector3(0f, 0f, 0f);

            boatEffect.DirectionalLight0.Enabled = true;
            boatEffect.DirectionalLight0.DiffuseColor = new Vector3(1.8f, 1.4f, 1f);
            boatEffect.DirectionalLight0.SpecularColor = new Vector3(0.4f, 0.5f, 0.5f);
            boatEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-5f, -2f, 5f));

            boatEffect.DirectionalLight1.Enabled = true;
            boatEffect.DirectionalLight0.DiffuseColor = new Vector3(0f, 0.2f, 0.5f);
            boatEffect.DirectionalLight0.SpecularColor = new Vector3(0f, 0f, 0f);
            boatEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(0f, 1f, 0f));

            boatEffect.DirectionalLight2.Enabled = false;

            boatEffect.LightingEnabled = true;
            boatEffect.TextureEnabled = false;

            boatEffect.View = viewMatrix;
            boatEffect.Projection = projectionMatrix;
            boatEffect.World = worldMatrix;


        }

        private static void InitWaterEffect()
        {
            //Initialice waterEffect
            waterEffect = new BasicEffect(Game1.graphicsDevice);

            waterEffect.Alpha = 1f;
            //waterEffect.DiffuseColor = new Vector3(0f, 0.3f, 0.8f);
            waterEffect.SpecularColor = new Vector3(1f, 1f, 1f);
            waterEffect.SpecularPower = 300.0f;
            waterEffect.AmbientLightColor = new Vector3(0f, 0f, 0f);
            
            waterEffect.DirectionalLight0.Enabled = true;
            waterEffect.DirectionalLight0.DiffuseColor = new Vector3(2.9f, 2.2f, 2f);
            waterEffect.DirectionalLight0.SpecularColor = new Vector3(5f, 2.5f, 1.1f);
            waterEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-5f, -4f, 5f));

            waterEffect.DirectionalLight1.Enabled = false;
            waterEffect.DirectionalLight1.DiffuseColor = new Vector3(0f, 0f, 0f);
            waterEffect.DirectionalLight1.SpecularColor = new Vector3(0.5f, 0.5f, 0.5f);
            waterEffect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(-3f, -1f, -3f));

            waterEffect.DirectionalLight2.Enabled = false;
            waterEffect.DirectionalLight2.DiffuseColor = new Vector3(0.1f, 0.2f, 0.5f);
            waterEffect.DirectionalLight2.SpecularColor = new Vector3(0.7f, 0.7f, 0.7f);
            waterEffect.DirectionalLight2.Direction = Vector3.Normalize(new Vector3(2f, -1f, -2f));


            waterEffect.LightingEnabled = true;
            waterEffect.TextureEnabled = true;


            waterEffect.View = viewMatrix;
            waterEffect.Projection = projectionMatrix;
            waterEffect.World = worldMatrix;

            
        }

        public static void setWaterTexture()
        {
            waterEffect.Texture = Water.waterTexture;
        }



        //Funktion för att skicka kameran mjukt mellan olika punkter
        public static void SoftSetCamera(Vector3 pos, Vector3 target)
        {
            cameraTimer = 1f;
            cameraMode = -1;
            customCamPos = pos;
            customCamTarget = target;

            camOldPos = camPos;
            camOldTarget = camTarget;
        }


        private static void GetCameraModeVectors(int camMode, ref Vector3 pos, ref Vector3 target)
        {
            switch (camMode)
            {
                case -1:  //Används för SoftSetCamera
                    target = customCamTarget;
                    pos = customCamPos;
                    break;

                case 1:
                    target = Players.player[0].position;
                    pos = new Vector3(500, 300, 1200);
                    break;
                case 2:
                    target = Players.player[1].position;
                    pos = new Vector3(500, 300, 1200);
                    break;
                case 3:
                    target = new Vector3(0.5f * Level.LENGTH_X, 30, 0.5f * Level.WIDTH_Z);
                    pos = new Vector3(Level.LENGTH_X * 0.5f - 800f * (float)Math.Sin(0.002 * cameraClock), 200 - 150 * (float)Math.Sin(0.002 * cameraClock), Level.WIDTH_Z * 0.5f + 700f * (float)Math.Cos(0.002 * cameraClock));
                    break;
                
                //Plats för flera kamerafunktioner
                case 4:
                    target = new Vector3(0.5f * Level.LENGTH_X, 30, 0.5f * Level.WIDTH_Z);
                    pos = new Vector3(Level.LENGTH_X * 0.5f - 750f * (float)Math.Sin(0.002 * cameraClock), 45, Level.WIDTH_Z * 0.5f + 450f * (float)Math.Cos(0.002 * cameraClock));
                    break;
                //case 5:
                //case 6:

                default:
                    target = new Vector3(0.5f * Level.LENGTH_X, 30, 0.5f * Level.WIDTH_Z);
                    pos = new Vector3(300f, 500, 1600f);
                    break;
                    
            }

        }


        public static void UpdateCamera(KeyboardState keyboard)
        {
            cameraClock++;
            bool camIsChanged = false;

            if (Game1.gameState == Game1.GameState.Gameplay)
            {
                if (keyboard.IsKeyDown(Keys.D1))
                    if (cameraMode != 1) { cameraMode = 1; camIsChanged = true; }
                if (keyboard.IsKeyDown(Keys.D2))
                    if (cameraMode != 2) { cameraMode = 2; camIsChanged = true; }
                if (keyboard.IsKeyDown(Keys.D3))
                    if (cameraMode != 3) { cameraMode = 3; camIsChanged = true; }
                if (keyboard.IsKeyDown(Keys.D4))
                    if (cameraMode != 4) { cameraMode = 4; camIsChanged = true; }
                if (keyboard.IsKeyDown(Keys.D5))
                    if (cameraMode != 5) { cameraMode = 5; camIsChanged = true; }
                if (keyboard.IsKeyDown(Keys.D6))
                    if (cameraMode != 6) { cameraMode = 6; camIsChanged = true; }
                
            }

            if (camIsChanged)
            {
                cameraTimer = 1f;
                camOldPos = camPos;
                camOldTarget = camTarget;
            }
            

            if (cameraTimer > 0)
            {
                cameraTimer -= 0.02f;
                float x = 2 * cameraTimer * cameraTimer - cameraTimer * cameraTimer * cameraTimer;
                
                GetCameraModeVectors(cameraMode, ref camHeadToPos, ref camTurnToTarget);
                
                camPos = x * camOldPos + (1 - x) * camHeadToPos;
                camTarget = x * camOldTarget + (1 - x) * camTurnToTarget;


            }
            else
                GetCameraModeVectors(cameraMode, ref camPos, ref camTarget);
                

        }










        public static void RenderAll()
        {
            //Update viwMatrix
            viewMatrix = Matrix.CreateLookAt(camPos, camTarget, Vector3.Up);

          

            //Sätter rasterState till solid-läge
            RasterizerState rasterState2 = new RasterizerState();
            rasterState2 = new RasterizerState();
            rasterState2.FillMode = FillMode.Solid;
            rasterState2.CullMode = CullMode.None;
            
            Game1.graphicsDevice.RasterizerState = rasterState2;

            Game1.graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
            Game1.graphicsDevice.BlendState = BlendState.Opaque;
            Game1.graphicsDevice.DepthStencilState = DepthStencilState.Default;
            

            //Rita upp modeller
            for (int n = 0; n < Players.MAX_PLAYERS; n++)
                if (Players.player[n].isActive)
                {
                    if (Players.player[n].isInTeamOne) boatEffect.DirectionalLight0.DiffuseColor = new Vector3(0f, 0.2f, 0.5f);
                    else boatEffect.DirectionalLight0.DiffuseColor = new Vector3(0.7f, 0.2f, 0.2f);
                    
                    DrawModel(BoatTypes.model[Players.player[n].type.index], Players.player[n].position, Players.player[n].rot, Players.player[n].roll, Players.player[n].pitch, new Vector3(1f), boatEffect);
                }


            DrawModel(Level.level, new Vector3(0f, 40f, 1000f), 0, 0, 0, new Vector3(0.1f * Level.LENGTH_X * MODEL_SCALE, 100 * MODEL_SCALE, 0.1f * Level.WIDTH_Z * MODEL_SCALE), levelEffect);
            
            //Sätter rasterState till wireframe-läge
            //RasterizerState rasterState = new RasterizerState();
            //rasterState.FillMode = FillMode.WireFrame;
            //Game1.graphicsDevice.RasterizerState = rasterState;
            
            //Rita upp vattenytan
            RenderWater();



            //Rita effekter
            Effects.DrawEffects(ref viewMatrix);

            


        }



        private static void RenderWater()
        {
            Water.UpdateWaterVertices();

            waterEffect.View = viewMatrix;
            
            foreach (EffectPass pass in waterEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                Game1.graphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                    PrimitiveType.TriangleList,
                    Water.node, 0, Water.NUM_Y * Water.NUM_X,
                    Water.triangleListIndices, 0, (Water.NUM_Y - 1) * (Water.NUM_X - 1) * 2);

            }
        
        }

        //Ritar upp
        private static void DrawModel(Model m, Vector3 position, float rot, Vector3 scale)
        {
            Matrix[] transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);
            
            Matrix transform = Matrix.CreateScale(scale) * Matrix.CreateRotationY(rot) * Matrix.CreateTranslation(position);

            

            foreach (ModelMesh mesh in m.Meshes)
            {   
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.View = viewMatrix;
                    effect.Projection = projectionMatrix;
                    effect.World = transforms[mesh.ParentBone.Index] * transform;

                    
                    

                }
                mesh.Draw();
            }
        }


        private static void DrawModel(Model m, Vector3 position, float rot, float roll, float pitch, Vector3 scale, BasicEffect customEffect)
        {
            Matrix[] transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);

            Matrix transform = Matrix.CreateScale(scale) * Matrix.CreateRotationZ(roll) * Matrix.CreateRotationX(-pitch) * Matrix.CreateRotationY(rot) * Matrix.CreateTranslation(position);



            foreach (ModelMesh mesh in m.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    customEffect.View = viewMatrix;
                    customEffect.Projection = projectionMatrix;
                    customEffect.World = transforms[0] * transform;
                    customEffect.CurrentTechnique.Passes[0].Apply();
                    Game1.graphicsDevice.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
                    Game1.graphicsDevice.Indices = meshPart.IndexBuffer;
                    

                    Game1.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);

                }
                //mesh.Draw();
            }
        }



    }
}
